package org.acghub.mtdb.core;

import org.acghub.mtdb.common.Config;
import org.acghub.mtdb.common.Lifecycle;
import org.acghub.mtdb.common.exception.LifecycleException;

import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

final class Lockeys implements Lifecycle<Config> {
    final static Lockeys INSTANCE = new Lockeys();
    private Bucket[] buckets;

    private LockeyType lockeyType;

    private int bucketShift;

    LockeyType getLockeyType() {
        return lockeyType;
    }

    @Override
    public void init(Config config) throws LifecycleException {
        this.lockeyType = LockeyType.of(config.getLockeyType());
        this.bucketShift = config.getLockBucketShift();
        if (lockeyType == null) {
            throw new LifecycleException("Invalid lockey type: " + config.getLockeyType());
        }
        if (bucketShift < 0 || bucketShift > 30) {
            throw new LifecycleException("Invalid bucket shift: " + bucketShift);
        }
        buckets = new Bucket[1 << bucketShift];
        for (int i = 0; i < buckets.length; ++i) {
            buckets[i] = new Bucket();
        }
    }

    @Override
    public void close() throws LifecycleException {
        for (int i = 0; i < buckets.length; ++i) {
            Bucket bucket = buckets[i];
            if (bucket != null) {
                bucket.clear();
            }
            buckets[i] = null;
        }
        buckets = null;
    }

    private Lockey get(Lockey lockey) {
        Transaction current = Transaction.current();
        if (current != null) {
            Lockey lockey1 = current.get(lockey);
            if (lockey1 != null) {
                return lockey1;
            }
        }
        int h = lockey.hashCode();
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        h = (h ^ (h >>> 16)) >>> (32 - bucketShift);
        return buckets[h].get(lockey);
    }

    Lockey getLockey(int lockId, Comparable key) {
        return get(lockeyType.newLockey(lockId, key));
    }

    enum LockeyType {
        FREE_LOCK {
            @Override
            Lockey newLockey(int id, Comparable key) {
                return new FreeLockey(id, key);
            }
        }, RW_LOCK {
            @Override
            Lockey newLockey(int id, Comparable key) {
                return new RWLockey(id, key);
            }
        };

        static LockeyType of(String lockeyType) {
            for (LockeyType type : LockeyType.values()) {
                if (type.name().equalsIgnoreCase(lockeyType)) {
                    return type;
                }
            }
            return null;
        }

        abstract Lockey newLockey(int id, Comparable key);
    }

    static class Bucket {
        private final Entry head = new Entry(null, null);

        public void clear() {
            Entry e = head.next;
            while (e != null) {
                Lockey lockey = e.get();
                if (lockey != null) {
                    lockey.deallocateLock();
                }
                e = e.next;
            }
        }

        synchronized Lockey get(Lockey key) {
            Entry e = head;
            while (e.next != null) {
                Lockey _key = e.next.get();
                if (_key == null) {
                    e.next = e.next.next;
                } else if (_key.equals(key)) {
                    return _key;
                } else {
                    e = e.next;
                }
            }
            e.next = new Entry(key, null);
            key.allocateLock();
            return key;
        }

        private static class Entry extends WeakReference<Lockey> {
            private Entry next;

            Entry(Lockey referent, Entry next) {
                super(referent);
                this.next = next;
            }
        }
    }

    /**
     * 无锁实现
     */
    static class FreeLockey extends Lockey {

        public FreeLockey(int id, Comparable key) {
            super(id, key);
        }

        @Override
        protected ReadWriteLock newRwlock() {
            return FreeRWLock.DEFAULT;
        }

        static class FreeRWLock implements ReadWriteLock {
            static final FreeRWLock DEFAULT = new FreeRWLock();

            @Override
            public Lock readLock() {
                return FreeLock.DEFAULT;
            }

            @Override
            public Lock writeLock() {
                return FreeLock.DEFAULT;
            }
        }

        static class FreeLock implements Lock {

            static final FreeLock DEFAULT = new FreeLock();

            @Override
            public void lock() {

            }

            @Override
            public void lockInterruptibly() {

            }

            @Override
            public boolean tryLock() {
                return true;
            }

            @Override
            public boolean tryLock(long time, TimeUnit unit) {
                return true;
            }

            @Override
            public void unlock() {

            }

            @Override
            public Condition newCondition() {
                throw new UnsupportedOperationException();
            }
        }
    }

    /**
     * 读写锁实现
     */
    static class RWLockey extends Lockey {
        public RWLockey(int id, Comparable key) {
            super(id, key);
        }

        @Override
        protected ReadWriteLock newRwlock() {
            return new ReentrantReadWriteLock();
        }

    }
}
